package model

import (
	"census/common/records"
	"census/common/utils"
	"census/context"
	"encoding/json"
	"fmt"
	"github.com/golib/helper"
	"github.com/golib/logger"
	"github.com/golib/mysql"
	"time"
)

const (
	TaskStatusUnknown = 0
	TaskStatusReady   = 1 //event ready
	TaskStatusBuffer  = 2 //event buffer
	TaskStatusStart   = 3 //event start
	TaskStatusSuccess = 4 //event success
	TaskStatusFail    = 5 //event fail
)

var taskStatusNote = map[int]string{
	TaskStatusUnknown: "未知",
	TaskStatusReady:   "就绪",
	TaskStatusBuffer:  "缓存",
	TaskStatusStart:   "开始",
	TaskStatusSuccess: "成功",
	TaskStatusFail:    "失败",
}

type TaskRequest interface{}

type TaskCallback func(*Task)

type Task struct {
	id          int
	uuid        string
	taskId      int
	appName     string
	status      int
	readyTime   int
	startTime   int
	isEnd       bool
	endTime     int
	taskFunc    []int
	data        TaskRequest
	explain     string
	curModuleId int
	callfuncs   map[int]func(args interface{})
}

func NewTask(appName string, taskId int, data TaskRequest) *Task {
	var task = new(Task)
	task.Init(appName, taskId, data)
	return task
}

func (t *Task) GetTaskUUId() string {
	return t.uuid
}

func (t *Task) GetTaskData() TaskRequest {
	return t.data
}

type TaskInfo struct {
	UUId      string      `json:"uuid"`
	Status    int         `json:"status"`
	Note      string      `json:"note"`
	StartTime int         `json:"startTime"`
	EndTime   int         `json:"endTime"`
	Duration  int         `json:"duration"`
	Data      interface{} `json:"data"`
	Explain   string      `json:"explain"`
}

func (t *Task) GetTaskInfo() *TaskInfo {
	var i = new(TaskInfo)
	i.UUId = t.GetTaskUUId()
	i.Status = t.status
	i.Note = taskStatusNote[t.status]
	i.Data = t.GetTaskData()
	i.Explain = t.explain
	i.StartTime = t.startTime
	i.EndTime = t.endTime
	var seconds = i.EndTime - i.StartTime
	if seconds > 0 {
		i.Duration = seconds
	}
	return i
}

func (t *Task) Init(appName string, taskId int, data TaskRequest) {
	t.uuid = utils.MakeUUID()
	t.status = TaskStatusReady
	t.taskId = taskId
	t.appName = appName
	t.data = data
	t.readyTime = int(time.Now().Unix())
	t.taskFunc = context.GetTaskModuleMap(taskId)
	t.callfuncs = make(map[int]func(interface{}))
	t.dbInit()
}

func (t *Task) Buffer() {
	t.status = TaskStatusBuffer
	t.dbBuffer()
	t.Emit(TaskStatusBuffer)
}

func (t *Task) Run() {
	//捕获业务异常
	defer t.recover()
	t.start()
	t.execute()
	t.end()
}

func (t *Task) recover() {
	if err := recover(); err != nil {
		t.status = TaskStatusFail
		t.explain = fmt.Sprintf("task exception: action (%v); taskUUId (%v); curModuleId (%v); message (%v);",
			t.taskId, t.uuid, t.curModuleId, err)
		t.logFail(t.explain, utils.PanicTrace())
		t.fail()
		t.end()
	}
}

//bind event
func (t *Task) On(event int, f func(args interface{})) {
	t.callfuncs[event] = f
}

//execute event
func (t *Task) Emit(event int) {
	if f, ok := t.callfuncs[event]; ok && f != nil {
		go f(t)
	}
}

func (t *Task) execute() {
	if t.taskFunc == nil {
		return
	}
	var moduleContext = make(map[int]*context.Parser)
	for _, moduleId := range t.taskFunc {
		if context.GetTaskModuleMap(t.taskId) != nil {
			t.curModuleId = moduleId
			c := new(context.Parser)
			c.LoadParams(t.data, moduleId)
			moduleContext[moduleId] = c
		}
	}
	for moduleId, c := range moduleContext {
		if moduleFunc := context.GetModuleFuncMap(moduleId); moduleFunc != nil {
			t.curModuleId = moduleId
			moduleFunc(c)
			t.logSuccess()
		}
	}
}

func (t *Task) start() {
	t.status = TaskStatusStart
	t.startTime = int(time.Now().Unix())
	//do it first
	ch, err := GetChannel(t.appName)
	if err == nil {
		ch.TaskProceed <- t
	}
	//next
	t.dbStart()
	t.Emit(TaskStatusStart)
}

func (t *Task) end() {
	if !t.isEnd {
		t.isEnd = true
		t.endTime = int(time.Now().Unix())
		if t.status == TaskStatusStart {
			t.success()
		}
		//do it first
		t.dbEnd()
		t.Emit(t.status)
		//next
		ch, err := GetChannel(t.appName)
		if err == nil {
			ch.TaskEnd <- t
		}
	}
}

func (t *Task) success() {
	ch, err := GetChannel(t.appName)
	if err == nil {
		ch.TaskSuccess <- t
	}
	t.status = TaskStatusSuccess
}

func (t *Task) fail() {
	ch, err := GetChannel(t.appName)
	if err == nil {
		ch.TaskFail <- t
	}
	t.status = TaskStatusFail
}

func (t *Task) logSuccess() {
	logger.Info("task-execute", t.GetTaskUUId(), "moduleId:[success]", t.curModuleId, "task data:", t.GetTaskData())
}

func (t *Task) logFail(message string, backTrace string) {
	logger.Warning("task-execute", t.GetTaskUUId(), "moduleId:[fail]", t.curModuleId,
		"task data:", t.GetTaskData(),
		"detail:", message,
		"backTrace\n", backTrace,
	)
}

func (t *Task) dbInit() {
	db := mysql.GetInstance(false)
	readyTime := helper.FormatTime(helper.TimeFormatYmdHis, t.readyTime)
	data, err := json.Marshal(t.data)
	if err != nil {
		return
	}
	stat := db.Insert(records.RecordNameCensusTaskInfo).
		Columns("uuid", "status", "note", "data", "ready_time", "action", "app_name").
		Value(t.uuid, t.status, taskStatusNote[t.status], string(data), readyTime, t.taskId, t.appName).
		Execute()
	t.id = stat.LastInsertId()
}

func (t *Task) dbBuffer() {
	if t.id == 0 {
		panic("[action: dbBuffer]: task db primary id is 0, db error. taskUUId:" + t.GetTaskUUId())
	}
	db := mysql.GetInstance(false)
	taskRecord := new(records.CensusTaskInfo)
	taskRecord.Id = t.id
	taskRecord.Status = t.status
	taskRecord.Note = taskStatusNote[t.status]
	db.SaveRecord(taskRecord, "status", "note")
}

func (t *Task) dbStart() {
	if t.id == 0 {
		panic("[action: dbStart]: task db primary id is 0, db error. taskUUId:" + t.GetTaskUUId())
	}
	db := mysql.GetInstance(false)
	taskRecord := new(records.CensusTaskInfo)
	taskRecord.Id = t.id
	taskRecord.Status = t.status
	taskRecord.Note = taskStatusNote[t.status]
	taskRecord.StartTime = helper.FormatTime(helper.TimeFormatYmdHis, t.startTime)
	db.SaveRecord(taskRecord, "status", "note", "startTime")
}

func (t *Task) dbEnd() {
	db := mysql.GetInstance(false)
	taskRecord := new(records.CensusTaskInfo)
	taskRecord.Id = t.id
	taskRecord.Status = t.status
	taskRecord.Note = taskStatusNote[t.status]
	taskRecord.EndTime = helper.FormatTime(helper.TimeFormatYmdHis, t.endTime)
	taskRecord.Explain = t.explain
	db.SaveRecord(taskRecord, "status", "note", "endTime", "explain")
}
